"""
主题: 利用Mixins扩展类功能
问题: 你有很多有用的方法，想使用它们来扩展其他类的功能。但是这些类并没有任何继承的关系。 
        因此你不能简单的将这些方法放入一个基类，然后被其他类继承。
提示 : 
"""
from collections import defaultdict

class LoggedMappingMixin:
    """
    Add logging to get/set/delete operations for debugging.
    """
    __slots__ = ()  # 混入类都没有实例变量，因为直接实例化混入类没有任何意义

    def __getitem__(self, key):
        print('Getting ' + str(key))
        return super().__getitem__(key)

    def __setitem__(self, key, value):
        print('Setting {} = {!r}'.format(key, value))
        return super().__setitem__(key, value)

    def __delitem__(self, key):
        print('Deleting ' + str(key))
        return super().__delitem__(key)

class SetOnceMappingMixin:
    '''
    Only allow a key to be set once.
    '''
    __slots__ = ()

    def __setitem__(self, key, value):
        if key in self:
            raise KeyError(str(key) + ' already set')
        return super().__setitem__(key, value)


class StringKeysMappingMixin:
    '''
    Restrict keys to strings only
    '''
    __slots__ = ()

    def __setitem__(self, key, value):
        if not isinstance(key, str):
            raise TypeError('keys must be strings')
        return super().__setitem__(key, value) 

def logged_mapping(cls):
    """第二种方式：使用类装饰器"""
    cls_getitem = cls.__getitem__
    cls_setitem = cls.__setitem__
    cls_delitem = cls.__delitem__

    def __getitem__(self, key):
        print('Getting ' + str(key))
        return cls_getitem(self, key)

    def __setitem__(self, key, value):
        print('Setting {} = {!r}'.format(key, value))
        return cls_setitem(self, key, value)

    def __delitem__(self, key):
        print('Deleting ' + str(key))
        return cls_delitem(self, key)

    cls.__getitem__ = __getitem__
    cls.__setitem__ = __setitem__
    cls.__delitem__ = __delitem__
    return cls

def recipe1():
    class LoggedDict(LoggedMappingMixin, dict):
        '''添加日志功能'''
        pass

    d = LoggedDict()
    d['x'] = 23
    print(f"{d['x'] = }")
    del d['x']

def recipe2():
    class SetOnceDefaultDict(SetOnceMappingMixin, defaultdict):
        '''字典健只能设置一次'''
        pass

    d = SetOnceDefaultDict(list)
    d['x'].append(2)
    d['x'].append(3)
    
    try:
        d['x'] = 23
    except KeyError as e:
        print(e)    

def recipe3():
    @logged_mapping
    class LoggedDict(dict):
        pass

    d = LoggedDict()
    d['x'] = 23
    print(f"{d['x'] = }")
    del d['x']

def main():
    print('recipe1'.center(20, '*'))
    recipe1()
    print('recipe2'.center(20, '*'))
    recipe2()
    print('recipe3'.center(20, '*'))
    recipe3()

if __name__ == '__main__':
    main()                